package com.qianfeng.smartdevice.service.impl;

import com.fasterxml.jackson.core.JsonProcessingException;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.github.pagehelper.PageHelper;
import com.github.pagehelper.PageInfo;
import com.qianfeng.smartdevice.cache.AreaCache;
import com.qianfeng.smartdevice.cache.CategoriesCache;
import com.qianfeng.smartdevice.constans.ResultCode;
import com.qianfeng.smartdevice.event.DeviceStatusChangeEvent;
import com.qianfeng.smartdevice.event.PowerCommandEvent;
import com.qianfeng.smartdevice.exceptions.QueryDataException;
import com.qianfeng.smartdevice.mapper.DeviceMapper;
import com.qianfeng.smartdevice.pojo.Area;
import com.qianfeng.smartdevice.pojo.Categories;
import com.qianfeng.smartdevice.pojo.Devices;
import com.qianfeng.smartdevice.service.IDeviceService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationContext;
import org.springframework.context.annotation.EnableAspectJAutoProxy;
import org.springframework.context.event.EventListener;
import org.springframework.scheduling.annotation.Async;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import java.util.List;
import java.util.Map;

@EnableAspectJAutoProxy(proxyTargetClass = true)//将代理方式改成 cglib
@Service
@Transactional
public class DeviceServiceImpl implements IDeviceService {
    @Autowired
    private DeviceMapper deviceMapper;
    @Autowired
    private CategoriesCache categoriesCache;
    @Autowired
    private AreaCache areaCache;


    private ObjectMapper objectMapper;

    private ApplicationContext context;

    @Autowired
    public void setObjectMapper(ObjectMapper objectMapper) {
        this.objectMapper = objectMapper;
    }

    @Autowired
    public void setContext(ApplicationContext context) {
        this.context = context;
    }


    @Override
    public PageInfo<Devices> findAllDevice(int page, int limit) {
        PageHelper.startPage(page, limit);
        //这个数据只有自己的基本信息,分类和区域信息都没有
        List<Devices> devicesList = deviceMapper.findAllDevices();
        //从缓存中获取到相关信息,设置到对象

        devicesList.forEach(devices -> {
            Long areaid = devices.getAreaid();//区域 id
            Long categoryid = devices.getCategoryid();//设备 id

            Area area = areaCache.getObj(areaid);//从缓存中获取指定的区域
            Categories categories = categoriesCache.getObj(categoryid);//从分类缓存中获取分类
            //将从缓存中获取到的区域和分类设置到当前遍历的对象上面
            devices.setArea(area);
            devices.setCategory(categories);
        });

        PageInfo<Devices> pageInfo = new PageInfo<>(devicesList);
        return pageInfo;
    }

    @Override
    public void sendContro(String uuid, String command) throws JsonProcessingException {
        //我们的目的是向设备发送命令
        //但是command 并不是真正的命令,它只是命令对应的一个 key,需要从分类中获取真正的命令
        //分类怎么获取? 每个设备对应一个分类,我们需要从设备中获取
        //设备怎么获取?我们有一个唯一的 uuid,根据 uuid 可以查询到设备的分类 id,然后我们有分类缓存,可以根据分类 id 从缓存中获取到分类的数据
        Long categoryId = deviceMapper.getCategoryIdByUUID(uuid);
        if (categoryId == null) {
            //说明传递的设备的 uuid 是错误的
            throw new QueryDataException("设备编号不存在", ResultCode.DATA_NOT_EXIST);
        }
        //在分类的对象中有一个属性记录了命令的映射,我们可以获取到
        Categories categories = categoriesCache.getObj(categoryId);
        String txcommand = categories.getTxcommand();//获取到指令的映射,是一个 json 字符串
        Map map = objectMapper.readValue(txcommand, Map.class);//将指令解析为 map
        Object o = map.get(command);//从 json 中读取到我们传递的命令对应的数据
        if (o instanceof Map) {
            Object type = ((Map<?, ?>) o).get("type");
            Object value = ((Map<?, ?>) o).get("value");
            if (type.toString().equals("1")) {
                //执行的是向继电器设备发送指令,发送的真正的指令内容就是 value
                //向设备发送数据是通过 webscoket 实现的,所以在这里我们需要找到这个设备对应的 websocket
                //方式 1 要么我们在这边获取到保存所有会话的 map,通过 uuid 拿连接,发送指令
                //方式 2 让 websocket 自己执行,在它内部可以直接获取到所有的会话的 map
                //我个人觉得方式 2 比较合适,因为可以不用和 websocket 直接建立关系,降低耦合度
                context.publishEvent(new PowerCommandEvent(uuid,value.toString()));
            } else if (type.toString().equals("2")) {
                //向颜色控制器发送指令
            }
        }

    }


//      注意,我们这么写可能会出现异常
//      org.springframework.beans.factory.BeanInitializationException: Failed to process @EventListener annotation on bean with name 'deviceServiceImpl';
//      nested exception is java.lang.IllegalStateException: Need to invoke method 'onEvent' declared on target class 'DeviceServiceImpl', but not found in any interface(s) of the exposed proxy type.
//       Either pull the method up to an interface or switch to CGLIB proxies by enforcing proxy-target-class mode in your configuration.
//      大体的意思就是 默认情况下 aop 使用的是 jdk 原生的代理,需要必须包含接口,拦截的方法必须都在接口中存在,现在我们这个方法不在接口中,所以出现了异常 ,要么声明到接口中,要么修改代理方式为 cglib

    @EventListener
    @Async//异步执行
    public void onEvent(DeviceStatusChangeEvent event) {
        String devicesuuid = event.getDevicesuuid();
        String status = event.getStatus();
        //更新状态
        int result = deviceMapper.updateStatusByUUID(devicesuuid, status);
        System.err.println("设备:" + devicesuuid + "的更新结果是:===>" + result);
    }

}








